﻿using System;
using StockModel;
using StockAccessors;
using Utils;

namespace StockEngine.RealTime
{
	public class AcquisitionSystem : IDisposable
	{
		#region Attributes

		private readonly AcquisitionScheduler _scheduler;
		private readonly AcquisitionEngine _engine;
		private readonly IStockDataAccessor _accessor;
		private readonly GroupInitializer _initializer;
		private readonly Acquisitor _acquisitor;

		private bool _initialized;
		private DateTime? _lastStart;

		#endregion

		#region Properties

		public ObservableCollection2<StockGroup> Groups { get; private set; }

		public AcquisitionSystemInformation Information { get; private set; }

		#endregion

		#region Public Methods

		public AcquisitionSystem(ExchangeMarketDataSource exchangeMarketDataSource, StockGroupDataSource stockGroupDataSource, QuotationDataSource quotationDataSource, IStockDataAccessor accessor, bool enableParallel)
		{
			_scheduler = new AcquisitionScheduler(exchangeMarketDataSource);
			_scheduler.StartRequired += this.Scheduler_StartRequired; // Local object
			_scheduler.StopRequired += this.Scheduler_StopRequired; // Local object

			_accessor = accessor;

			_initializer = new GroupInitializer(_accessor, stockGroupDataSource, quotationDataSource, enableParallel);
			_acquisitor = new Acquisitor(_accessor, _initializer.Groups, quotationDataSource);
			_engine = new AcquisitionEngine(_initializer.Groups, _initializer, _acquisitor);

			this.Groups = new ObservableCollection2<StockGroup>();
			this.Information = new AcquisitionSystemInformation(_scheduler, _engine, _acquisitor);
		}

		public void InitializeSystem()
		{
			if (_initialized)
			{
				throw new InvalidOperationException("The AcquisitionSystem should be initialized only once. It is already initialized.");
			}

			this.Groups.AddRange(_initializer.Groups); // Add the groups before initializing them to catch stock item added events
			_initialized = true;
		}

		public void Start()
		{
			if (!_initialized)
			{
				throw new InvalidOperationException("The AcquisitionSystem should be initialized before starting it");
			}

			try
			{
				this.Information.State = AcquisitionSystemState.Enabling;

				if (_lastStart == null || _lastStart.Value.Date != DateTime.Now.Date) 
				{
					_initializer.ExecuteFirstTimeInitialization();
					_scheduler.Initialize();					
				}

				_scheduler.Start();
				_lastStart = DateTime.Now;
			}
			catch (Exception)
			{
				this.Information.State = AcquisitionSystemState.Disabled;
				throw;
			}
		}

		public void Stop()
		{
			try
			{
				this.Information.State = AcquisitionSystemState.Disabling;
				_scheduler.Stop();
			}
			catch (Exception)
			{
				this.Information.State = AcquisitionSystemState.Disabled;
				throw;
			}
		}

		#endregion

		#region Event Handlers

		private void Scheduler_StartRequired(object sender, System.EventArgs e)
		{
			_lastStart = DateTime.Now;
			_accessor.Connect();
			_engine.Start();
		}

		private void Scheduler_StopRequired(object sender, StopRequiredEventArgs e)
		{
			if (e.WaitEngineReady)
			{
				DateTime startWait = DateTime.Now;
				while (!_engine.IsReadyToStop() && DateTime.Now.Subtract(startWait) < Properties.Settings.Default.MaxWaitForEngineReadyToStop)
				{
					System.Threading.Thread.Sleep(Properties.Settings.Default.IntervalForClosedQuote);
				}
			}

			_accessor.Disconnect();
			_engine.Stop();
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				_engine.Dispose();
				_scheduler.Dispose();

				this.Information.Dispose();
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~AcquisitionSystem()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
